#ifndef SINK_H
#define SINK_H

#include "util.hpp"
#include <memory>
#include <iomanip>
#include <ctime>
#include <stdexcept>
#include <fstream>
#include <chrono>

namespace Log
{
    /// @brief 日志落地基类
    /// @note  抽象类, 重写void log(const char *data, size_t len)
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;

        LogSink() {}

        virtual ~LogSink() {}

        /// @brief
        /// @param data
        /// @param len
        virtual void log(const char *data, size_t len) = 0;
    };

    /// @brief 落地方向：标准输出
    class StdoutSink : public LogSink
    {
    public:
        /// @brief 写入标准输出
        /// @param data 日志消息
        /// @param len 写入大小
        void log(const char *data, size_t len) override
        {
            std::cout.write(data, len);
        }
    };

    /// @brief 落地方向：指定文件
    class FileSink : public LogSink
    {
    public:
        /// @brief 构造, 传入日志写入的路径
        /// @param pathname 日志写入路径
        FileSink(const std::string &pathname) : _pathname(pathname)
        {
        }
        /// @brief 写入文件
        /// @param data 日志消息
        /// @param len  写入大小
        /// @throw runtime_error: 打不开文件、无法写入文件
        void log(const char *data, size_t len) override
        {
            // 确保日志目录存在
            Log::util::File::createDirectory(util::File::path(_pathname));

            try
            {
                // 尝试以二进制追加模式打开文件
                _ofs.open(_pathname, std::ios::binary | std::ios::app);
                if (!_ofs)
                {
                    throw std::runtime_error("Failed to open file: " + _pathname);
                }

                // 将数据写入文件
                _ofs.write(data, len);
                _ofs.flush(); // 确保数据写入磁盘
                if (!_ofs)
                {
                    throw std::runtime_error("Failed to write data to file: " + _pathname);
                }

                _ofs.close();
            }
            catch (const std::exception &e)
            {
                std::cerr << "Log error: " << e.what() << std::endl;
                if (_ofs.is_open())
                {
                    _ofs.close(); // 确保文件流关闭
                }
                throw; // 重新抛出异常，允许上层处理
            }
        }

    private:
        std::string _pathname; // 日志写入的路径
        std::ofstream _ofs;
    };

    /**
     * @brief 基于文件大小的滚动日志记录器
     *
     * 当日志文件达到指定大小时自动创建新文件继续记录
     * 文件名格式：基础文件名-年月日-时分秒-次数.log（示例：mylog-20230221-143205-1.log）
     *
     * 特性：
     * - 线程不安全（需外部同步）
     * - 使用追加模式写入
     * - 精确控制文件大小（考虑已有文件内容）
     * - 标准化时间戳格式
     * - 异常安全设计
     */
    class RollBySizeSink : public LogSink {
        public:
            RollBySizeSink(const std::string& basename, size_t max_size)
                : _basename(basename),
                  _name_count(0),
                  _max_fsize(max_size)
                {
                std::string pathname = createNewfile();
                util::File::createDirectory(util::File::path(pathname));
        
                try {
                    // 以追加二进制模式打开，保留已有内容
                    _ofs.open(pathname, std::ios::binary | std::ios::app);
        
                    if (!_ofs) {
                        throw std::runtime_error("Failed to open file: " + pathname);
                    }
        
                    // 获取文件实际大小
                    _cur_fsize = util::File::getFileSize(pathname);
                } catch (const std::exception& e) {
                    std::cerr << "Log initialization error: " << e.what() << std::endl;
                    if (_ofs.is_open()) _ofs.close();
                    throw; // 传播异常给调用者处理
                }
            }
        
            void log(const char* data, size_t len) override {
                // 预检查是否需要滚动（避免写入后超出限制）
                if (_cur_fsize + len > _max_fsize) {
                    rollFile();
                }
        
                // 执行实际写入
                _ofs.write(data, len);
                _ofs.flush(); // 确保数据写入磁盘
                
                // 验证写入操作状态
                if (!_ofs.good()) {
                    throw std::runtime_error("File write operation failed");
                }
        
                // 更新当前文件大小
                _cur_fsize += len;
            }
        
        private:
            void rollFile() {
                _ofs.close();
        
                std::string pathname = createNewfile();
        
                try {
                    // 以追加模式打开新文件（允许继续已存在的日志文件）
                    _ofs.open(pathname, std::ios::binary | std::ios::app);
        
                    if (!_ofs) {
                        throw std::runtime_error("Failed to open new file: " + pathname);
                    }
        
                    // 获取新文件的初始大小（处理可能存在的旧日志文件）
                    _cur_fsize = util::File::getFileSize(pathname);
                } catch (const std::exception& e) {
                    throw std::runtime_error("File rotation failed: " + std::string(e.what()));
                }
            }
        
            std::string createNewfile() {
                time_t t = util::Date::getTime();
                struct tm lt;
                localtime_r(&t, &lt);
        
                std::stringstream filename;
                filename << _basename << "-"
                         << std::put_time(&lt, "%Y%m%d-%H%M%S")
                         << "-"
                         << (_name_count ++)
                         << ".log";
                return filename.str();
            }
        
            
        
        private:
            std::string _basename;  // 基础文件名
            size_t _name_count;     // 防止同一时间内瞬间写入大量数据导致的创建文件名相同
            std::ofstream _ofs;     // 文件输出流
            size_t _max_fsize;      // 最大文件尺寸限制
            size_t _cur_fsize;      // 当前已写入字节数
        };

    /**
     * @brief 基于时间的滚动日志记录器（默认按天滚动）
     *
     * 特性：
     * - 自动滚动
     * - 线程不安全（需外部同步）
     * - 支持追加模式
     * - 自动创建目录结构
     */
    class RollByTimeSink : public LogSink
    {
    public:
        /**
         * @brief 构造函数初始化日志系统
         * @param basename 基础文件名（不含日期和扩展名）
         * @param sec 滚动时间间隔（秒），默认一天（86400秒）
         */
        explicit RollByTimeSink(const std::string &basename, time_t sec = 86400)
            : _basename(basename), _sec(sec)
        {
            initializeNextRollTime();
            rollFile(); // 初始化时立即创建首个文件
        }

        void log(const char *data, size_t len) override
        {
            if (shouldRoll())
            {
                rollFile();
            }
            _ofs.write(data, len);
            _ofs.flush();
            if (!_ofs.good())
            {
                throw std::runtime_error("Log write failed");
            }
        }

    private:
        void initializeNextRollTime()
        {
            time_t now = time(nullptr);
            _next_roll_time = ((now / _sec) + 1) * _sec;
        }

        bool shouldRoll() const
        {
            return time(nullptr) >= _next_roll_time;
        }

        void rollFile()
        {
            closeCurrentFile();
            createNewFile();
            initializeNextRollTime();
        }

        void closeCurrentFile()
        {
            if (_ofs.is_open())
                _ofs.close();
        }

        void createNewFile()
        {
            std::string pathname = generateFilename();
            util::File::createDirectory(util::File::path(pathname));
            _ofs.open(pathname, std::ios::binary | std::ios::app);
            if (!_ofs)
            {
                throw std::runtime_error("Cannot open log file: " + pathname);
            }
        }

        std::string generateFilename() const
        {
            time_t now = time(nullptr);
            struct tm local_tm;
            localtime_r(&now, &local_tm);

            std::string format;
            if (_sec >= 86400)
            { // 按天
                format = "%Y%m%d";
            }
            else if (_sec >= 3600)
            { // 按小时
                format = "%Y%m%d-%H";
            }
            else
            { // 按分钟/秒
                format = "%Y%m%d-%H%M%S";
            }

            std::stringstream ss;
            ss << _basename << "-" << std::put_time(&local_tm, format.c_str()) << ".log";
            return ss.str();
        }

    private:
        std::string _basename;      // 基础文件名
        std::ofstream _ofs;
        time_t _sec;                // 时间阈值
        time_t _next_roll_time;     // 下次滚动时间
    };

    class SinkFactory
    {
    public:
        template <typename SinkType, typename... Args>
        static LogSink::ptr create(Args &&...args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}

#endif